#include "logger.h"
#include <filesystem>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <cstdarg>
#include <cstdio>
#include <ctime>

namespace cores
{
    namespace loggers
    {
        Logger *Logger::globalInstance{nullptr};

        static const std::vector<std::string> vectLabels = {"[LOG]", "[INFO]", "[DEBU]", "[WARN]", "[ERRO]", "[ALL]"};
        static const std::vector<std::string> vectFiles = {"log", "info", "debug", "warn", "error", "all"};

        Logger::Logger()
            : isRunning_(false), consoleLog_(1), level_(ELog)
        {
            if (Logger::globalInstance == nullptr)
                Logger::globalInstance = this;

            std::thread thread(Logger::onRun, this);
            thread.detach();
        }

        Logger::~Logger()
        {
            while (!queue_.empty())
            {
                delete queue_.front();
                queue_.pop();
            }

            if (Logger::globalInstance == this)
                Logger::globalInstance = nullptr;
        }

        void Logger::stop()
        {
            isRunning_ = false;            
        }

        void Logger::setLogDir(const std::string &logDir)
        {
            lock_.lock();
            logDir_ = logDir;
            if (!logDir_.empty())
            {
                if (logDir_.back() != '/' && logDir_.back() != '\\')
                    logDir_.push_back('/');
            }

            lock_.unlock();

            auto path = std::filesystem::path(logDir_);
            if (std::filesystem::exists(path) == false)
            {
                std::filesystem::create_directory(logDir_);
            }
        }

        void Logger::setConsoleLog(Level level)
        {
            level_ = level;
        }

        Logger *Logger::instance()
        {
            if (Logger::globalInstance == nullptr)
            {
                static Logger logger;
            }

            return Logger::globalInstance;
        }

        inline std::string msecondToString(uint64_t ms)
        {
            std::time_t timestamp = ms / 1000;
            char buffer[80] = {};
            std::tm *info = std::localtime(&timestamp);
            std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", info);
            return std::string(buffer) + std::string(".") + std::to_string(ms % 1000);
        }

        void Logger::print(const char *file, int line, const char *func, int level, Data *data)
        {
            if (data == nullptr)
            {
                assert(false);
                return;
            }

            if (level_ >= vectLabels.size() || level < 0)
            {
                delete data;
                assert(false);
                return;
            }

            auto duration = std::chrono::system_clock::now().time_since_epoch();
            auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
            data->level_ = level;
            data->line_ = line;
            data->time_ = ms;
            data->func_ = func;
            data->file_ = file;
            data->threadId_ = std::this_thread::get_id();

            if (consoleLog_ == 1)
            {
                std::stringstream ss;
                ss << vectLabels[level] << "[" << msecondToString(data->time_) << "]["
                   << data->file_ << ":" << data->func_ << ":" << data->line_ << "]" << data->buffer_.data() << std::endl;
                std::cout << ss.str();
            }

            lock_.lock();
            queue_.push(data);
            lock_.unlock();
            cv_.notify_one();
        }

        void Logger::onRun(Logger *logger)
        {
            if (logger == nullptr)
            {
                assert(false);
                return;
            }

            logger->run();
        }

        void Logger::run()
        {
            assert(vectLabels.size() == EMax);
            assert(vectFiles.size() == EMax);

            // char buffer[80] = {};
            std::tm *info = nullptr;
            std::time_t timestamp{0};
            uint64_t currentDayIdx{0};
            uint64_t lastDayIdx{0};
            isRunning_ = true;
            Data *data{nullptr};
            // auto isEmpty = false;

            while (isRunning_)
            {
                std::unique_lock<std::mutex> lock(mutex_);
                lock_.lock();
                if (queue_.empty())
                {
                    lock_.unlock();
                    cv_.wait(lock);
                    continue;
                }
                lock_.unlock();

                if (!logDir_.empty())
                {
                    timestamp = std::time(0);
                    info = std::localtime(&timestamp);
                    currentDayIdx = (info->tm_year + 1900) * 10000 + (info->tm_mon + 1) * 100 + info->tm_mday;
                    if (lastDayIdx != currentDayIdx)
                    {
                        lastDayIdx = currentDayIdx;
                        lock_.lock();
                        if (logDir_.empty())
                            logDir_ = "./";
                        lock_.unlock();
                        std::string dayDir = logDir_ + std::to_string(currentDayIdx);
                        if (std::filesystem::is_directory(dayDir) == false)
                            std::filesystem::create_directory(dayDir);
                        for (int i = 0; i < EMax; ++i)
                        {
                            auto &file = files_[i];
                            if (files_[i].is_open())
                                files_[i].close();
                            logPaths_[i] = dayDir + "/" + vectFiles[i] + ".log";
                            files_[i].open(logPaths_[i], std::ios::out | std::ios::app);
                        }
                    }
                }
            
                while (!queue_.empty())
                {
                    lock_.lock();
                    // if (queue_.empty())
                    // {
                    //     lock_.unlock();
                    //     break;
                    // }
                    data = queue_.front();
                    queue_.pop();
                    lock_.unlock();

                    if (data == nullptr)
                    {
                        assert(false);
                        continue;
                    }
                    int level = data->level_;
                    // std::cout << __LINE__ << ":" << level << std::endl;
                    assert(level >= 0);
                    if (level >= vectLabels.size())
                        assert(false);

                    std::stringstream ss;
                    ss << vectLabels[level] << "[" << msecondToString(data->time_) << "][0x" << std::hex << data->threadId_ << std::dec << "]["
                       << data->file_ << ":" << data->func_ << ":" << data->line_ << "] " << data->buffer_.data() << std::endl;
                    if (consoleLog_ == 2)
                    {                        
                        std::cout << ss.str();
                    }
                    // std::cout << __FILE__ << ":" << __LINE__ << std::endl;
                    if (logPaths_[level].empty() == false)
                    {
                        // std::cout << __FILE__ << ":" << __LINE__ << std::endl;
                        if (files_[level].is_open())
                        {
                            // std::cout << __LINE__ << ":" << level << ":" << ss.str() <<  std::endl;
                            files_[level] << ss.str();
                            files_[level].flush();
                        }

                        if (files_[EAll].is_open())
                        {
                            // std::cout << __LINE__ << ":" << level << ":" << ss.str() <<  std::endl;
                            files_[EAll] << ss.str();
                            files_[EAll].flush();
                        }
                    }

                    delete data;

                    // std::cout << __FILE__ << ":" << __FUNCTION__ << ":" << __LINE__ << ":"
                            //   << "success write to log files" << std::endl;
                }
            }

            for (auto i = 0; i < EMax; i++)
            {
                if (files_[i].is_open())
                {
                    files_[i].close();
                    // std::cout << __FILE__ << ":" << __LINE__ << std::endl;
                }
            }
        }

        void Logger::Log(const char *file, int line, const char *func, const char *format, ...)
        {
            auto data = new Data;
            auto &buffer = data->buffer_;
            int count = 1;
            int maxSize = 0;
            va_list ap;
            for (;;)
            {
                maxSize = count * 256;
                buffer.resize(maxSize + 1, 0);

                va_start(ap, format);
                data->size_ = vsnprintf(buffer.data(), maxSize, format, ap);
                va_end(ap);
                if (data->size_ >= 0 && data->size_ < maxSize)
                {
                    break;
                }
                ++count;
            }
            buffer.data()[data->size_] = 0;
            Logger::instance()->print(file, line, func, ELog, data);
        }

        void Logger::Info(const char *file, int line, const char *func, const char *format, ...)
        {
            auto data = new Data;
            auto &buffer = data->buffer_;
            int count = 1;
            int maxSize = 0;
            va_list ap;
            for (;;)
            {
                maxSize = count * 256;
                buffer.resize(maxSize + 1, 0);

                va_start(ap, format);
                data->size_ = vsnprintf(buffer.data(), maxSize, format, ap);
                va_end(ap);
                if (data->size_ >= 0 && data->size_ < maxSize)
                {
                    break;
                }
                ++count;
            }
            buffer.data()[data->size_] = 0;
            Logger::instance()->print(file, line, func, EInfo, data);
        }

        void Logger::Debug(const char *file, int line, const char *func, const char *format, ...)
        {
            auto data = new Data;
            auto &buffer = data->buffer_;
            int count = 1;
            int maxSize = 0;
            va_list ap;
            for (;;)
            {
                maxSize = count * 256;
                buffer.resize(maxSize + 1, 0);

                va_start(ap, format);
                data->size_ = vsnprintf(buffer.data(), maxSize, format, ap);
                va_end(ap);
                if (data->size_ >= 0 && data->size_ < maxSize)
                {
                    break;
                }
                ++count;
            }
            buffer.data()[data->size_] = 0;
            Logger::instance()->print(file, line, func, EDebug, data);
        }

        void Logger::Warn(const char *file, int line, const char *func, const char *format, ...)
        {
            auto data = new Data;
            auto &buffer = data->buffer_;
            int count = 1;
            int maxSize = 0;
            va_list ap;
            for (;;)
            {
                maxSize = count * 256;
                buffer.resize(maxSize + 1, 0);

                va_start(ap, format);
                data->size_ = vsnprintf(buffer.data(), maxSize, format, ap);
                va_end(ap);
                if (data->size_ >= 0 && data->size_ < maxSize)
                {
                    break;
                }
                ++count;
            }
            buffer.data()[data->size_] = 0;
            Logger::instance()->print(file, line, func, EWarn, data);
        }

        void Logger::Error(const char *file, int line, const char *func, const char *format, ...)
        {
            auto data = new Data;
            auto &buffer = data->buffer_;
            int count = 1;
            int maxSize = 0;
            va_list ap;
            for (;;)
            {
                maxSize = count * 256;
                buffer.resize(maxSize + 1, 0);

                va_start(ap, format);
                data->size_ = vsnprintf(buffer.data(), maxSize, format, ap);
                va_end(ap);
                if (data->size_ >= 0 && data->size_ < maxSize)
                {
                    break;
                }
                ++count;
            }
            buffer.data()[data->size_] = 0;
            Logger::instance()->print(file, line, func, EError, data);
        }
    }
}